[C#] これだけ押さえておけば大丈夫!?LINQ拡張メソッドまとめ
はじめに
こんにちは。最近C#と戯れているモバイルアプリサービス部の加藤潤です。
今回は今更ではありますが、C#のLINQ拡張メソッドを復習してみたいと思います。
開発環境
本記事中のプログラムは以下の環境で動作確認しています。
- Visual Studio for Mac Preview 2(7.0 build 560)
- .NETコンソールプロジェクト
- ターゲットフレームワーク Mono / .NET 4.5
LINQの便利メソッドは拡張メソッドで定義されている
LINQでよく使うWhere
などのコレクション操作の便利メソッドはSystem.Linq
名前空間のEnumerable
クラスに拡張メソッドとして定義されています。
準備
操作対象のコレクションとして以下のように1から10の整数値を用意しました。 以降、このコレクションに対してLINQ拡張メソッドを実行していきます。
// 1から10の整数値を取得(型はIEnumerable<int>) var values = Enumerable.Range(1, 10);
また、コレクション操作後の値を出力するために以下の拡張メソッドを定義しました。
public static class Extension { public static void Write<T>(this IEnumerable<T> source) { WriteLine(string.Join(",", source)); } public static void Write<T>(this T obj) { WriteLine(obj); } }
Where
Where
は指定した条件を満たす要素を抽出するために使います。
// 偶数のみ抽出 values.Where(v => v % 2 == 0).Write(); // 2,4,6,8,10
Take
Take
は先頭から指定した数分の要素を取得します。
// 先頭から3つ分を取得 values.Take(3).Write(); // 1,2,3
TakeWhile
TakeWhile
は指定した条件を満たす間、要素を取得します。
values.TakeWhile(v => v < 9).Write(); // 1,2,3,4,5,6,7,8
「指定した条件を満たす間」であるため、コレクションがソートされていない場合は例えば以下のような結果となります。
var list = new List<int>() { 1, 10, 8 }; list.TakeWhile(v => v < 9).Write(); // 1
Skip
Skip
は先頭から指定した数分要素をスキップし、残りの要素を返します。
// 先頭の3つをスキップ values.Skip(3).Write(); // 4,5,6,7,8,9,10
SkipWhile
SkipWhile
は指定した条件を満たす間、要素をスキップします。
// 9未満である間要素をスキップ values.SkipWhile(v => v < 9).Write(); // 9,10
「指定した条件を満たす間」であるため、コレクションがソートされていない場合は例えば以下のような結果となります。
var list = new List<int>() { 1, 10, 8 }; list.SkipWhile(v => v < 9).Write(); // 10, 8
Any
Any
は要素が含まれているかどうかを確認するために使います。
// 要素が1つでも含まれていればtrue, 空であればfalse values.Any().Write(); // True
また、条件を指定することで、その条件を満たす要素が含まれているかどうかを確認することも可能です。
// 条件を満たす要素が1つでも含まれていればtrue values.Any(v => v == 10).Write(); // True values.Any(v => v == 11).Write(); // False
All
All
は全ての要素が指定した条件を満たすかどうかを確認するために使います。
// 全ての要素が偶数かどうか values.All(v => v % 2 == 0).Write(); // False
Max
Max
は最大値を得るために使います。
// 最大値 values.Max().Write(); // 10
Min
Min
は最小値を得るために使います。
// 最小値 values.Min().Write(); // 1
Sum
Sum
は合計値を得るために使います。
// 合計値 values.Sum().Write(); // 55
Average
Average
は平均値を得るために使います。
// 平均値 values.Average().Write(); // 5.5
First(OrDefault)
First
は先頭要素を取得するために使います。
values.First().Write(); // 1
また、条件を指定することで、その条件を満たす先頭の要素を取得することも可能です。
values.First(v => v % 3 == 0).Write(); // 3
同じようなメソッドにFirstOrDefault
があります。こちらは要素が存在する場合はFirst
と結果は同じです。
values.FirstOrDefault().Write(); // 1 values.FirstOrDefault(v => v % 3 == 0).Write(); // 3
First
とFirstOrDefault
の違いは要素が存在しない場合です。
First
は要素が存在しない場合例外が発生しますが、FirstOrDefault
は例外は発生せず既定値(例えばintの場合は0)となります。
既定値について
参照型および null許容型の既定値はnull
です。
値型の既定値については既定値の一覧表 (C# リファレンス)をご覧ください。
Last(OrDefault)
First
が先頭要素を取得するのに対して、Last
は最後の要素を取得します。
values.Last().Write(); // 10 values.Last(v => v % 3 == 0).Write(); // 9 values.LastOrDefault().Write(); // 10 values.LastOrDefault(v => v % 3 == 0).Write(); // 9
Last
とLastOrDefault
の違いはFirst
とFirstOrDefault
の違いと同様です。
ElementAt(OrDefault)
ElementAt
は指定したインデックス位置にある要素を取得します。
values.ElementAt(5).Write(); // 6 values.ElementAtOrDefault(5).Write(); // 6
ElementAt
とElementAtOrDefault
の違いは指定したインデックスが範囲外の場合です。ElementAt
は例外が発生しますが、ElementAtOrDefault
は既定値となります。
Single(OrDefault)
Single
は唯一の要素を取得するメソッドです。要素が複数存在する場合は例外が発生します。
values.Single(); // 例外(System.InvalidOperationException)発生 values.Single(v => v == 10).Write(); // 10 values.SingleOrDefault(); // 例外(System.InvalidOperationException)発生 values.SingleOrDefault(v => v == 10).Write(); // 10
Single
とSingleOrDefault
の違いはコレクションが空の場合です。
Single
は例外が発生しますが、SingleOrDefault
は例外は発生せず既定値(例えばintの場合は0)となります。
values = Enumerable.Empty<int>(); values.Single(); // 例外(System.InvalidOperationException)発生 values.SingleOrDefault().Write(); // 0
Select
Select
は要素に変換関数を適用した結果を得ることができます。
この変換関数次第で様々な結果を返すことができます。
例えば以下のようにすれば、各要素を10倍したコレクションを返すことが出来ます。
// 各要素を10倍する values.Select(v => v * 10).Write(); // 10,20,30,40,50,60,70,80,90,100
また、例えば以下のようなParent
クラスとChild
クラスがあったとします。
public class Parent { public string Name { get; set; } public IEnumerable<Child> Children { get; set; } } public class Child { public string Name { get; set; } }
そしてこんな感じにParent
のリストがあったとします。
var p1 = new Parent(); p1.Name = "Yamada Taro"; var p1Chidren = new List<Child> { new Child { Name = "Yamada Hanako" }, new Child { Name = "Yamada Kenji" } }; p1.Children = p1Chidren; var p2 = new Parent(); p2.Name = "Saito Haruka"; var p2Chidren = new List<Child> { new Child { Name = "Saito Mamoru" }, new Child { Name = "Saito Yuki" } }; p2.Children = p2Chidren; var pList = new List<Parent> { p1, p2 };
Select
を使えば以下のように特定のプロパティのみ取得することができます。
// Nameプロパティのみ取得 pList.Select(p => p.Name).Write(); // Yamada Taro,Saito Haruka
それ以外にも匿名型を返すことも可能なので便利です。
// 匿名型を返す pList.Select(p => new { Id = p.Name });
SelectMany
SelectMany
はSelect
の結果を平坦化(フラット化)することができます。
例えばSelect
を使ってChildren
プロパティのみ取得すると、Children
プロパティがコレクションであるため、結果がコレクションのコレクションとなり、Childのインスタンスにアクセスするためには2重ループしなければいけません。
// selectResultの型はIEnumerable<IEnumerable<Child>> var selectResult = pList.Select(p => p.Children); foreach (var children in selectResult) { foreach (var child in children) { // Childにアクセス } }
これがSelectMany
を使うと以下のようなフラットなコレクションにしてくれます。
// IEnumerable<Child> var selectManyResult = pList.SelectMany(p => p.Children); foreach (var child in selectManyResult) { // Childにアクセス }
OrderBy
OrderBy
は昇順に並び替えるメソッドです。
var values = new List<int> { 3, 9, 1, 45, 2, 6, 4 }; values.OrderBy(v => v).Write(); // 1,2,3,4,6,9,45
OrderByDescending
OrderByDescending
は降順に並び替えるメソッドです。
var values = new List<int> { 3, 9, 1, 45, 2, 6, 4 }; values.OrderByDescending(v => v).Write(); // 45,9,6,4,3,2,1
おわりに
LINQ拡張メソッドを復習してみました。今回は「1つ1つのメソッドでどういうことができるのか」という観点でまとめてみましたが、LINQはメソッドチェーンで書くことができますし、必要に応じて遅延実行されます。その辺りの仕組みや挙動を理解するにはこちらの書籍がオススメです。私ももう一度読み返してみたいと思います。